
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { makeInner, normalizeToArray } from '../util/model.js';
import { assert, bind, each, eqNaN, extend, hasOwn, indexOf, isArrayLike, keys, reduce } from 'zrender/lib/core/util.js';
import { cloneValue } from 'zrender/lib/animation/Animator.js';
import Displayable from 'zrender/lib/graphic/Displayable.js';
import { getAnimationConfig } from './basicTransition.js';
import { Path } from '../util/graphic.js';
import { warn } from '../util/log.js';
import { TRANSFORMABLE_PROPS } from 'zrender/lib/core/Transformable.js';
var LEGACY_TRANSFORM_PROPS_MAP = {
	position: ['x', 'y'],
	scale: ['scaleX', 'scaleY'],
	origin: ['originX', 'originY']
};
var LEGACY_TRANSFORM_PROPS = keys(LEGACY_TRANSFORM_PROPS_MAP);
var TRANSFORM_PROPS_MAP = reduce(TRANSFORMABLE_PROPS, function (obj, key) {
	obj[key] = 1;
	return obj;
}, {});
var transformPropNamesStr = TRANSFORMABLE_PROPS.join(', '); // '' means root

export var ELEMENT_ANIMATABLE_PROPS = ['', 'style', 'shape', 'extra'];

var transitionInnerStore = makeInner();

function getElementAnimationConfig(animationType, el, elOption, parentModel, dataIndex) {
	var animationProp = animationType + 'Animation';
	var config = getAnimationConfig(animationType, parentModel, dataIndex) || {};
	var userDuring = transitionInnerStore(el).userDuring; // Only set when duration is > 0 and it's need to be animated.

	if (config.duration > 0) {
		// For simplicity, if during not specified, the previous during will not work any more.
		config.during = userDuring ? bind(duringCall, {
			el: el,
			userDuring: userDuring
		}) : null;
		config.setToFinal = true;
		config.scope = animationType;
	}

	extend(config, elOption[animationProp]);
	return config;
}

export function applyUpdateTransition(el, elOption, animatableModel, opts) {
	opts = opts || {};
	var dataIndex = opts.dataIndex,
		isInit = opts.isInit,
		clearStyle = opts.clearStyle;
	var hasAnimation = animatableModel.isAnimationEnabled(); // Save the meta info for further morphing. Like apply on the sub morphing elements.

	var store = transitionInnerStore(el);
	var styleOpt = elOption.style;
	store.userDuring = elOption.during;
	var transFromProps = {};
	var propsToSet = {};
	prepareTransformAllPropsFinal(el, elOption, propsToSet);
	prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet);
	prepareShapeOrExtraAllPropsFinal('extra', elOption, propsToSet);

	if (!isInit && hasAnimation) {
		prepareTransformTransitionFrom(el, elOption, transFromProps);
		prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps);
		prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps);
		prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps);
	}

	propsToSet.style = styleOpt;
	applyPropsDirectly(el, propsToSet, clearStyle);
	applyMiscProps(el, elOption);

	if (hasAnimation) {
		if (isInit) {
			var enterFromProps_1 = {};
			each(ELEMENT_ANIMATABLE_PROPS, function (propName) {
				var prop = propName ? elOption[propName] : elOption;

				if (prop && prop.enterFrom) {
					if (propName) {
						enterFromProps_1[propName] = enterFromProps_1[propName] || {};
					}

					extend(propName ? enterFromProps_1[propName] : enterFromProps_1, prop.enterFrom);
				}
			});
			var config = getElementAnimationConfig('enter', el, elOption, animatableModel, dataIndex);

			if (config.duration > 0) {
				el.animateFrom(enterFromProps_1, config);
			}
		} else {
			applyPropsTransition(el, elOption, dataIndex || 0, animatableModel, transFromProps);
		}
	} // Store leave to be used in leave transition.

	updateLeaveTo(el, elOption);
	styleOpt ? el.dirty() : el.markRedraw();
}
export function updateLeaveTo(el, elOption) {
	// Try merge to previous set leaveTo
	var leaveToProps = transitionInnerStore(el).leaveToProps;

	for (var i = 0; i < ELEMENT_ANIMATABLE_PROPS.length; i++) {
		var propName = ELEMENT_ANIMATABLE_PROPS[i];
		var prop = propName ? elOption[propName] : elOption;

		if (prop && prop.leaveTo) {
			if (!leaveToProps) {
				leaveToProps = transitionInnerStore(el).leaveToProps = {};
			}

			if (propName) {
				leaveToProps[propName] = leaveToProps[propName] || {};
			}

			extend(propName ? leaveToProps[propName] : leaveToProps, prop.leaveTo);
		}
	}
}
export function applyLeaveTransition(el, elOption, animatableModel, onRemove) {
	if (el) {
		var parent_1 = el.parent;
		var leaveToProps = transitionInnerStore(el).leaveToProps;

		if (leaveToProps) {
			// TODO TODO use leave after leaveAnimation in series is introduced
			// TODO Data index?
			var config = getElementAnimationConfig('update', el, elOption, animatableModel, 0);

			config.done = function () {
				parent_1.remove(el);
				onRemove && onRemove();
			};

			el.animateTo(leaveToProps, config);
		} else {
			parent_1.remove(el);
			onRemove && onRemove();
		}
	}
}
export function isTransitionAll(transition) {
	return transition === 'all';
}

function applyPropsDirectly(el, // Can be null/undefined
	allPropsFinal, clearStyle) {
	var styleOpt = allPropsFinal.style;

	if (!el.isGroup && styleOpt) {
		if (clearStyle) {
			el.useStyle({}); // When style object changed, how to trade the existing animation?
			// It is probably complicated and not needed to cover all the cases.
			// But still need consider the case:
			// (1) When using init animation on `style.opacity`, and before the animation
			//     ended users triggers an update by mousewhel. At that time the init
			//     animation should better be continued rather than terminated.
			//     So after `useStyle` called, we should change the animation target manually
			//     to continue the effect of the init animation.
			// (2) PENDING: If the previous animation targeted at a `val1`, and currently we need
			//     to update the value to `val2` and no animation declared, should be terminate
			//     the previous animation or just modify the target of the animation?
			//     Therotically That will happen not only on `style` but also on `shape` and
			//     `transfrom` props. But we haven't handle this case at present yet.
			// (3) PENDING: Is it proper to visit `animators` and `targetName`?

			var animators = el.animators;

			for (var i = 0; i < animators.length; i++) {
				var animator = animators[i]; // targetName is the "topKey".

				if (animator.targetName === 'style') {
					animator.changeTarget(el.style);
				}
			}
		}

		el.setStyle(styleOpt);
	}

	if (allPropsFinal) {
		// Not set style here.
		allPropsFinal.style = null; // Set el to the final state firstly.

		allPropsFinal && el.attr(allPropsFinal);
		allPropsFinal.style = styleOpt;
	}
}

function applyPropsTransition(el, elOption, dataIndex, model, // Can be null/undefined
	transFromProps) {
	if (transFromProps) {
		var config = getElementAnimationConfig('update', el, elOption, model, dataIndex);

		if (config.duration > 0) {
			el.animateFrom(transFromProps, config);
		}
	}
}

function applyMiscProps(el, elOption) {
	// Merge by default.
	hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
	hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore);

	if (el instanceof Displayable) {
		hasOwn(elOption, 'invisible') && (el.invisible = elOption.invisible);
	}

	if (el instanceof Path) {
		hasOwn(elOption, 'autoBatch') && (el.autoBatch = elOption.autoBatch);
	}
} // Use it to avoid it be exposed to user.

var tmpDuringScope = {};
var transitionDuringAPI = {
	// Usually other props do not need to be changed in animation during.
	setTransform: function (key, val) {
		if (process.env.NODE_ENV !== 'production') {
			assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.');
		}

		tmpDuringScope.el[key] = val;
		return this;
	},
	getTransform: function (key) {
		if (process.env.NODE_ENV !== 'production') {
			assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.');
		}

		return tmpDuringScope.el[key];
	},
	setShape: function (key, val) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var el = tmpDuringScope.el;
		var shape = el.shape || (el.shape = {});
		shape[key] = val;
		el.dirtyShape && el.dirtyShape();
		return this;
	},
	getShape: function (key) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var shape = tmpDuringScope.el.shape;

		if (shape) {
			return shape[key];
		}
	},
	setStyle: function (key, val) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var el = tmpDuringScope.el;
		var style = el.style;

		if (style) {
			if (process.env.NODE_ENV !== 'production') {
				if (eqNaN(val)) {
					warn('style.' + key + ' must not be assigned with NaN.');
				}
			}

			style[key] = val;
			el.dirtyStyle && el.dirtyStyle();
		}

		return this;
	},
	getStyle: function (key) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var style = tmpDuringScope.el.style;

		if (style) {
			return style[key];
		}
	},
	setExtra: function (key, val) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var extra = tmpDuringScope.el.extra || (tmpDuringScope.el.extra = {});
		extra[key] = val;
		return this;
	},
	getExtra: function (key) {
		if (process.env.NODE_ENV !== 'production') {
			assertNotReserved(key);
		}

		var extra = tmpDuringScope.el.extra;

		if (extra) {
			return extra[key];
		}
	}
};

function assertNotReserved(key) {
	if (process.env.NODE_ENV !== 'production') {
		if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
			throw new Error('key must not be "' + key + '"');
		}
	}
}

function duringCall() {
	// Do not provide "percent" until some requirements come.
	// Because consider thies case:
	// enterFrom: {x: 100, y: 30}, transition: 'x'.
	// And enter duration is different from update duration.
	// Thus it might be confused about the meaning of "percent" in during callback.
	var scope = this;
	var el = scope.el;

	if (!el) {
		return;
	} // If el is remove from zr by reason like legend, during still need to called,
	// because el will be added back to zr and the prop value should not be incorrect.

	var latestUserDuring = transitionInnerStore(el).userDuring;
	var scopeUserDuring = scope.userDuring; // Ensured a during is only called once in each animation frame.
	// If a during is called multiple times in one frame, maybe some users' calculation logic
	// might be wrong (not sure whether this usage exists).
	// The case of a during might be called twice can be: by default there is a animator for
	// 'x', 'y' when init. Before the init animation finished, call `setOption` to start
	// another animators for 'style'/'shape'/'extra'.

	if (latestUserDuring !== scopeUserDuring) {
		// release
		scope.el = scope.userDuring = null;
		return;
	}

	tmpDuringScope.el = el; // Give no `this` to user in "during" calling.

	scopeUserDuring(transitionDuringAPI); // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`,
	// consider the issue that the prop might be incorrect when return to "normal" state.
}

function prepareShapeOrExtraTransitionFrom(mainAttr, fromEl, elOption, transFromProps) {
	var attrOpt = elOption[mainAttr];

	if (!attrOpt) {
		return;
	}

	var elPropsInAttr = fromEl[mainAttr];
	var transFromPropsInAttr;

	if (elPropsInAttr) {
		var transition = elOption.transition;
		var attrTransition = attrOpt.transition;

		if (attrTransition) {
			!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});

			if (isTransitionAll(attrTransition)) {
				extend(transFromPropsInAttr, elPropsInAttr);
			} else {
				var transitionKeys = normalizeToArray(attrTransition);

				for (var i = 0; i < transitionKeys.length; i++) {
					var key = transitionKeys[i];
					var elVal = elPropsInAttr[key];
					transFromPropsInAttr[key] = elVal;
				}
			}
		} else if (isTransitionAll(transition) || indexOf(transition, mainAttr) >= 0) {
			!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
			var elPropsInAttrKeys = keys(elPropsInAttr);

			for (var i = 0; i < elPropsInAttrKeys.length; i++) {
				var key = elPropsInAttrKeys[i];
				var elVal = elPropsInAttr[key];

				if (isNonStyleTransitionEnabled(attrOpt[key], elVal)) {
					transFromPropsInAttr[key] = elVal;
				}
			}
		}
	}
}

function prepareShapeOrExtraAllPropsFinal(mainAttr, elOption, allProps) {
	var attrOpt = elOption[mainAttr];

	if (!attrOpt) {
		return;
	}

	var allPropsInAttr = allProps[mainAttr] = {};
	var keysInAttr = keys(attrOpt);

	for (var i = 0; i < keysInAttr.length; i++) {
		var key = keysInAttr[i]; // To avoid share one object with different element, and
		// to avoid user modify the object inexpectedly, have to clone.

		allPropsInAttr[key] = cloneValue(attrOpt[key]);
	}
}

function prepareTransformTransitionFrom(el, elOption, transFromProps) {
	var transition = elOption.transition;
	var transitionKeys = isTransitionAll(transition) ? TRANSFORMABLE_PROPS : normalizeToArray(transition || []);

	for (var i = 0; i < transitionKeys.length; i++) {
		var key = transitionKeys[i];

		if (key === 'style' || key === 'shape' || key === 'extra') {
			continue;
		}

		var elVal = el[key];

		if (process.env.NODE_ENV !== 'production') {
			checkTransformPropRefer(key, 'el.transition');
		} // Do not clone, animator will perform that clone.

		transFromProps[key] = elVal;
	}
}

function prepareTransformAllPropsFinal(el, elOption, allProps) {
	for (var i = 0; i < LEGACY_TRANSFORM_PROPS.length; i++) {
		var legacyName = LEGACY_TRANSFORM_PROPS[i];
		var xyName = LEGACY_TRANSFORM_PROPS_MAP[legacyName];
		var legacyArr = elOption[legacyName];

		if (legacyArr) {
			allProps[xyName[0]] = legacyArr[0];
			allProps[xyName[1]] = legacyArr[1];
		}
	}

	for (var i = 0; i < TRANSFORMABLE_PROPS.length; i++) {
		var key = TRANSFORMABLE_PROPS[i];

		if (elOption[key] != null) {
			allProps[key] = elOption[key];
		}
	}
}

function prepareStyleTransitionFrom(fromEl, elOption, styleOpt, transFromProps) {
	if (!styleOpt) {
		return;
	}

	var fromElStyle = fromEl.style;
	var transFromStyleProps;

	if (fromElStyle) {
		var styleTransition = styleOpt.transition;
		var elTransition = elOption.transition;

		if (styleTransition && !isTransitionAll(styleTransition)) {
			var transitionKeys = normalizeToArray(styleTransition);
			!transFromStyleProps && (transFromStyleProps = transFromProps.style = {});

			for (var i = 0; i < transitionKeys.length; i++) {
				var key = transitionKeys[i];
				var elVal = fromElStyle[key]; // Do not clone, see `checkNonStyleTansitionRefer`.

				transFromStyleProps[key] = elVal;
			}
		} else if (fromEl.getAnimationStyleProps && (isTransitionAll(elTransition) || isTransitionAll(styleTransition) || indexOf(elTransition, 'style') >= 0)) {
			var animationProps = fromEl.getAnimationStyleProps();
			var animationStyleProps = animationProps ? animationProps.style : null;

			if (animationStyleProps) {
				!transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
				var styleKeys = keys(styleOpt);

				for (var i = 0; i < styleKeys.length; i++) {
					var key = styleKeys[i];

					if (animationStyleProps[key]) {
						var elVal = fromElStyle[key];
						transFromStyleProps[key] = elVal;
					}
				}
			}
		}
	}
}

function isNonStyleTransitionEnabled(optVal, elVal) {
	// The same as `checkNonStyleTansitionRefer`.
	return !isArrayLike(optVal) ? optVal != null && isFinite(optVal) : optVal !== elVal;
}

var checkTransformPropRefer;

if (process.env.NODE_ENV !== 'production') {
	checkTransformPropRefer = function (key, usedIn) {
		if (!hasOwn(TRANSFORM_PROPS_MAP, key)) {
			warn('Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' + 'Only `' + keys(TRANSFORM_PROPS_MAP).join('`, `') + '` are permitted.');
		}
	};
}